/** 
 * omm.only for modern mobile一个超级简单的仿jquery接口设计的现代手机浏览器的js操作库
 * author:QinYongcheng
 *
 */
//创建全局闭包.核心
;
(function(win) {
	var $ = (function() {
		var _$ = function() {
				return new $.fn.init(arguments);
			}
			/**
			 * 定义一个安装库名的全局函数，避免命名空间冲突
			 */
		win.bindAPI = function(scope, name) {
			scope[name] = _$;
			return scope[name];
		}
		if(win.ego && win.omm) {
			return _$;
		} else { //如果ego不存在，绑定_ego到win
			return win.ego = win.omm = _$;
		}
	})();
	$.fn = $.prototype = {
		constructor: $,
		init: function(args) {
			this.elems = $.select(args[0]);
		},
		elems: [],
		stacks: {},
		stacksIdx: 1,
		method: function(name, fn) {
			this.prototype[name] = fn;
			return this;
		},
		size: function() {
			return this.elems.length;
		},
		end: function(selector, history) {
			if(selector) {
				history || (history = this.stacksIdx);
				if(history != "exist") {
					this.stacks[history] = this.stacks[this.stacksIdx] = this.elems;
					this.elems = $.select(selector);
					this.stacksIdx++;
				} else {
					return $.select(selector);
				}
			}
			return this;
		},
		back: function(history, del) {
			del = del || true;
			history || (history = this.stacksIdx && (this.stacksIdx--));
			this.elems = this.stacks[history];
			del && (delete this.stacks[history]);
			return this;
		}
	}
	$.fn.init.prototype = $.prototype;
	// 扩展属性   后续 Object.assign ECMAScript 6  替换  https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/assign   https://github.com/sindresorhus/deep-assign/blob/master/index.js
	$.fn.extend = $.extend = function() {
		var copy = function self(from, to) {
			for(var property in from) {
				//自身属性
				if(from.hasOwnProperty(property)) {
					// 若destination[property]和sourc[property]都是对象，则递归
					if(deep && $.isObject(from[property]) && $.isObject(to[property])) {
						self(from[property], to[property]);
					};
					to[property] = from[property];
				}
			}
		}

		if(!arguments.length) return {};
		var args = arguments,
			target = args[0] || {},
			length = args.length,
			deep = false,
			i = 1;
		// Handle a deep copy situation
		if(typeof target === "boolean") {
			deep = target;
			// skip the boolean and the target
			target = args[1] || {};
			i++;
		}
		// extend $ itself if only one argument is passed
		if(length == i) {
			target = this;
			i--;
		}
		for(; i < length; i++) {
			if(args[i]) {
				copy(args[i], target);
			};
		}
		return target;
	};
	var class2type = {};
	["Boolean", "Number", "String", "Function", "Array", "Date", "RegExp", "Object", "Error"].forEach(function(name, i) {
		class2type["[object " + name + "]"] = name.toLowerCase();
	});

	function type(obj) {
		if(obj == null) return obj + "";
		return typeof obj === "object" || typeof obj === "function" ?
			class2type[toString.call(obj)] || "object" :
			typeof obj;
	}
	["Boolean", "String", "Function", "Array", "Date", "RegExp", "Object", "Error"].forEach(function(name, i) {
		$["is" + name] = function(obj) {
			return type(obj) == name.toLowerCase();
		}
	});

	/**
	 * 静态属性方法
	 */
	$.extend({
		error: function(msg) {
			throw new Error(msg);
		},
		/**
		 * 查询元素，返回元素数组
		 * @param {Object} obj
		 */
		select: function(selector, ctx) {
			var dom = [],
				ctx = ctx || document,
				filterEl = function(e) {
					return e.nodeType && [1, 9].indexOf(e.nodeType) > -1 || selector === window;
				};
			if(typeof selector == "string") {
				if($.isHtml(selector)) {
					dom.push($.fragment(selector));
				} else {
					dom = [].slice.call(ctx.querySelectorAll.call(ctx, selector)).filter(function(e, i) {
						return filterEl(e);
					});
				};
			} else if(selector instanceof $) {
				dom = selector.elems;
			} else if(filterEl(selector)) {
				dom.push(selector);
			} else if($.isLikeArray(selector)) {
				dom = [].slice.call(selector).filter(function(e, i) {
					return filterEl(e);
				});
			}
			return dom;
		},
		noop: function() {},
		isLikeArray: function(obj) {
			return obj ? typeof obj.length == 'number' : null;
		},
		/*
		isObject: function(obj) {
			return type(obj) == 'object';
		},
		isFunction: function(fn) {
			return type(fn) == 'function';
		},
		isArray: function(arr) {
			return Array.isArray ? Array.isArray(arr) : type(arr) === 'array';
		},
		isString: function(obj) {
			return typeof obj == 'string';
		},
		*/
		isWindow: function(win) {
			return win && win == win.window;
		},
		isDocument: function(doc) {
			return doc && doc.nodeType == doc.DOCUMENT_NODE;
		},
		/**
		 * 对象是否为null或者undefined
		 */
		isNull: function(obj) {
			return obj === null || obj === undefined
		},
		/**
		 * 字符串是否为空
		 */
		isBlank: function(str) {
			return $.isNull(str) || str.trim() == "";
		},
		/**
		 *  检查对象是否为空对象,即对象没有任何属性.判断对象是否为空即没有属性，通常用于判断{}
		 */
		isEmpty: function(obj) {
			if(!$.isObject(obj))
				$.error("参数必须为Object类型");
			for(var attr in obj) {
				return false;
			}
			return true;
		},
		/**
		 * 判断值是否为数字
		 */
		isNumber: function(val) {
			return !$.isBlank(val) && (!isNaN(val) || typeof val == "number");
		},
		/**
		 * 
		 * @param {Object} value
		 */
		isEmail: function(value) {
			return /^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/.test(value);
		},
		isHtml: function(val) {
			return /^<([^>\s]+)[^>]*>(.*?<\/\\1>)?$/.test(val);
		},
		containHtml: function(val) {
			return /<[^>]+>/g.test(val);
		},
		isUrl: function(value) {
			return /^((http|https):\/\/(\w+:{0,1}\w*@)?(\S+)|)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?$/.test(value);
		},
		/**
		 * 获取16位数的唯一字符串
		 */
		uuid: function() {
			return(((1 + Math.random()) * 0x10000) | 0).toString(16).substring(1);
		},
		/** 
		 *将css属性连字符“-”格式的字符字符串转换为驼峰模式	
		 */
		camelize: function(str) {
			return str.replace(/^-ms-/, "ms-").replace(/-([a-z])/ig, function(all, letter) {
				return letter.toUpperCase();
			});
		},
		/**
		 * 取得随机数
		 * @param {Number} min 下限
		 * @param {Number} max 上限
		 * @return {Boolean}  字符串为空返回true
		 */
		random: function(min, max) {
			if(min > max) {
				min = min - max;
				max = min + max;
				min = max - min;
			}
			var result = Math.round(Math.random() * (max - min) + min);
			return parseInt(result);
		},
		/**
		 * 字符串以某字符开头
		 * @param {Object} str
		 * @param {Object} searchStr
		 */
		startWith: function(str, searchStr) {
			return new RegExp("^" + searchStr).test(str);
		},
		endWith: function(str, searchStr) {
			return new RegExp(searchStr + "$").test(str);
		},
		serialize: function(params, sep) {
			if(!params) return "";
			var p = [];
			$.each(params, function(k, v) {
				p.push(k + "=" + v);
			}, params, true);
			return p.join(sep ? sep : "&");
		},
		/**
		 * 添加url参数
		 */
		addUrlParams: function(url, params) {
			if(!url) return "";
			var p = $.serialize(params);
			if(!p) return url;
			//url = url.replace("\?", "\\?");
			if(url.indexOf("?") > 0) {
				if(!$.endWith(url, "&") && !$.endWith(url, "\\?")) {
					url = url + "&";
				}
			} else {
				url = url + "?";
			}
			return url + p;
		},
		/**
		 * 获取URL参数。
		 */
		getUrlParam: function(name, searchStr) {
			var reg = new RegExp("(^|&)" + name + "=([^&]*)(&|$)"),
				url = searchStr || location.search,
				r = url.substr(1).match(reg);
			return r != null ? unescape(r[2]) : null;
		},
		/**
		 * 通过id查询元素
		 */
		$id: function(id) {
			return "string" == typeof(id) ? document.getElementById(id) : null;
		},
		attr: function(el, name, val) {
			if(!el) return null;
			if($.isNull(val)) {
				return el.getAttribute(name) || null;
			} else {
				el.setAttribute(name, val)
			}
		},
		data: function(e, n, v) {
			if(!e) return null;
			n = $.camelize(n);
			//获取属性
			if($.isNull(v)) {
				return e.dataset[n] || null;
			} else { //设置值
				e.dataset[n] = v;
			}
		},

		/**
		 * 遍历数组/对象元素 
		 * @param {Object} data
		 * @param {Object} fn
		 * @param {Object} context
		 */
		each: function(data, fn, context, ownProperty) {
			context = context ? context : data;
			//数组类型 
			if($.isLikeArray(data)) {
				for(var i = 0, len = data.length; i < len; i++) {
					var ret = fn.call(context, i, data[i]);
					//有返回值且返回值不为boolean类型或者为false
					if(ret !== undefined && (typeof ret != "boolean" || !ret)) {
						return ret;
					}
				}
			} else {
				for(var k in data) {
					if(!ownProperty || (ownProperty && data.hasOwnProperty(k))) { //跳过继承属性
						var ret = fn.call(context, k, data[k]);
						//有返回值且返回值不为boolean类型或者为false
						if(ret !== undefined && (typeof ret != "boolean" || !ret)) {
							return ret;
						}
					}
				}
			}
			return context;
		},
		map: function(elements, callback) {
			var value, values = [],
				i, key
			if($.isLikeArray(elements))
				for(i = 0; i < elements.length; i++) {
					value = callback(elements[i], i)
					if(value != null) values.push(value)
				}
			else
				for(key in elements) {
					value = callback(elements[key], key)
					if(value != null) values.push(value)
				}
			return values.length > 0 ? [].concat.apply([], values) : values;
		},

		/**
		 * 将字符串首字母转换为大写
		 * @param {Object} str
		 */
		upperFirst: function(str) {
			return str.replace(/\b(\w)|\s(\w)/g, function(m) {
				return m.toUpperCase();
			});
		},
		matches: function(element, selector) {
			if(!element || element.nodeType !== 1 || !selector) return false;
			//在SELECTORS API Level 2规范中，为DOM节点添加了一个方法，主要是用来判断当前DOM节点不否能完全匹配对应的CSS选择器规则；如果匹配成功，返回true，反之则返回false。语法如下：
			//element.matches(String selector);
			var matchesSelector = element.matches ||
				element.matchesSelector || element.webkitMatchesSelector || element.msMatchesSelector || element.mozMatchesSelector || element.oMatchesSelector;
			if(matchesSelector) return matchesSelector.call(element, selector);
		},
		contains: function(parent, node) {
			if(parent && !node) return document.documentElement.contains(parent)
			return parent !== node && parent.contains(node)
		},
		unique: function(array) {
			return [].filter.call(array, function(item, idx) {
				return array.indexOf(item) == idx
			})
		},
		// parents、 nextAll等方法调用
		// nodes 节点集合或者单个节点
		// selector 选择器，过滤用
		// dir 获取集合比如`parentNode`
		dir: function(nodes, selector, dir) {
			var ancestors = [];
			while(nodes.length > 0) nodes = $.map(nodes, function(node) {
				if((node = node[dir]) && !$.isDocument(node) && ancestors.indexOf(node) < 0) {
					$.matches(node, selector || "*") && (ancestors.push(node));
					return node
				}
			});
			return ancestors;
		},
		fragment: function(html) {
			var nodes, divTemp = document.createElement("div"),
				fragment = document.createDocumentFragment();
			divTemp.innerHTML = html;
			// 此时div.childNodes就是我们需要的节点了
			nodes = divTemp.childNodes;
			$.each(nodes, function(i, e) {
				// 容器fragment加载克隆的节点 - 克隆的作用是保证nodes的完整
				fragment.appendChild(e.cloneNode(true));
			}, this);
			// 据说下面这样子世界会更清净
			nodes = null;
			divTemp = null;
			return fragment;
		},
		/**
		 * 一个简单js的HTML模板引擎
		 * http://ejohn.org/  
		 * 
		 * http://blog.jobbole.com/56689/
		 * 
		 * http://krasimirtsonev.com/blog/article/Javascript-template-engine-in-just-20-line
		 * 
		 * http://www.cnblogs.com/dolphinX/p/3489269.html
		 */
		template: function(html, data) {
			if(!data) return html;
			//以#开头
			if($.startWith(html, "#")) {
				html = $.$id(html.substr(1)).innerHTML;
			}
			var re = /<#(.+?)#>/g,
				reExp = /(^( )?(var|if|for|else|switch|case|break|{|}|;))(.*)?/g,
				code = 'with(obj) { var r=[];\n',
				cursor = 0,
				result,
				match;
			var add = function(line, js) {
				js ? (code += line.match(reExp) ? line + '\n' : 'r.push(' + line + ');\n') :
					(code += line != '' ? 'r.push("' + line.replace(/"/g, '\\"') + '");\n' : '');
				return add;
			}
			while(match = re.exec(html)) {
				add(html.slice(cursor, match.index))(match[1], true);
				cursor = match.index + match[0].length;
			}
			add(html.substr(cursor, html.length - cursor));
			code = (code + 'return r.join(""); }').replace(/[\r\t\n]/g, ' ');
			try {
				result = new Function('obj', code).apply(data, [data]);
			} catch(err) {
				console.error("'" + err.message + "'", " in \n\nCode:\n", code, "\n");
			}
			return cursor <= 0 ? html : result;
		}
	});
	/**
	 * 原型方法
	 */
	$.fn.extend({
		get: function(index, attr) {
			var e = index === undefined ? [].slice.call(this.elems) : this.elems[index >= 0 ? index : index + this.elems.length];
			return !attr ? e : e ? e[attr] : null;
		},
		/**
		 * 遍历元素,如果元素返回false则跳出循环并还回，否则返回ego对象
		 * @param {Object} fn
		 */
		each: function(fn) {
			return $.each(this.elems, fn, this);
		},
		map: function(fn, history) {
			return this.end(this.elems.map(function(e, i) {
				return fn.call(e, i, e);
			}, this), history);
		},
		//遍历查找对象
		filter: function(selector, history) {
			if(isFunction(selector)) return this.not(this.not(selector, history), history)
			return this.end(this.elems.filter(function(e, i) {
				return $.matches(e, selector);
			}, this), history);
		},
		not: function(selector, history) {
			var nodes = [];
			if($.isFunction(selector) && selector.call !== undefined) {
				this.each(function(i, e) {
					if(!selector.call(this, i, e)) nodes.push(e);
				});
			} else {
				var excludes = typeof selector == 'string' ? this.filter(selector, "exist") :
					($.isLikeArray(selector) && $.isFunction(selector.item)) ? [].slice.call(selector) : $.select(selector);
				this.elems.forEach(function(el) {
					if(excludes.indexOf(el) < 0) nodes.push(el)
				})
			}
			return this.end(nodes, history)
		},
		siblings: function(selector, history) {
			var nodes = [];
			this.each(function(i, e) {
				[].filter.call(e.parentNode.children, function(child) {
					if(child !== e && $.matches(child, selector || "*")) nodes.push(child);
				});
			});
			return this.end(nodes, history);
		},
		parent: function(selector, history) {
			var nodes = [];
			this.each(function(i, e) {
				[].filter.call(e.parentNode, function(child) {
					if($.matches(child, selector || "*")) nodes.push(child);
				});
			});
			return this.end(nodes, history);
		},
		parents: function(selector, history) {
			return this.end($.dir(this.elems, selector, 'parentNode'), history);
		},
		children: function(selector, history) {
			var nodes = [];
			[].filter.call(this.pluck('children'), function(item, idx) {
				[].map.call(item, function(el) {
					if(el && el.nodeType == 1 && $.matches(el, selector || "*")) nodes.push(el)
				})
			});
			return this.end(nodes, history);
		},
		find: function(selector, history) {
			var nodes = [];
			this.each(function(i, e) {
				nodes = nodes.concat($.select(selector, e));
			});
			return this.end(nodes, history);
		},
		eq: function(idx, history) {
			return idx === -1 ? this.end(this.elems.slice(idx), history) : this.end(this.elems.slice(idx, +idx + 1), history)
		},
		//属性
		pluck: function(property) {
			return [].map.call(this.elems, function(el) {
				return el[property]
			})
		},
		/**
		 * 选择的元素对象是否有指定的类
		 * @param {Object} theClass
		 */
		hasClass: function(theClass) {
			var ret = this.each(function(index, target) {
				if(target.classList.contains(theClass)) {
					return false;
				}
			});
			return typeof ret == "boolean";
		},

		/**
		 * 添加类
		 * @param {Object} theClass
		 */
		addClass: function(theClass) {
			return this.each(function(index, target) {
				if(!this.hasClass(theClass)) {
					target.classList.add(theClass);
				}
			});
		},
		toggleClass: function(theClass) {
			return this.each(function(index, target) {
				target.classList.toggle(theClass);
			});
		},

		/**
		 * 移除类
		 * @param {Object} theClass
		 */
		removeClass: function(theClass) {
			return this.each(function(index, target) {
				target.classList.remove(theClass);
			});
		},
		append: function(html, data) {
			return this.insert(html, "append", data);
		},
		prepend: function(html, data) {
			return this.insert(html, "prepend", data);
		},
		before: function(html, data) {
			return this.insert(html, "before", data);
		},
		after: function(html, data) {
			return this.insert(html, "after", data);
		},
		/**
		 * 插入html到指定位置。纯JS为DOM添加html字符串：appendHTML方法和prependHTML方法
		 * http://www.myexception.cn/HTML-CSS/1876216.html
		 * http://www.zhangxinxu.com/wordpress/2013/05/js-dom-basic-useful-method/
		 * @param {Object} where: after、before、append、prepend
		 * @param {Object} html
		 * @param {Object} data 可选.如果存在则用模板渲染HTML
		 */
		insert: function(html, where, data) {
			if(arguments.length < 2) return this;
			var p = {
				after: "afterend",
				before: "beforebegin",
				append: "beforeend",
				prepend: "afterbegin"
			};
			this.each(function(i, e) {
				//以#开头
				e.insertAdjacentHTML(p[where], $.template ? $.template(html, data) : html);
			});
			return this;
		},
		html: function(html, data) {
			if($.isNull(html)) return this.get(0, "innerHTML") || "";
			this.each(function(i, e) {
				e.innerHTML = $.template ? $.template(html, data) : html;
				//性能？http://www.myexception.cn/HTML-CSS/900694.html
			});
			return this;
		},
		attr: function(n, v) {
			return this.each(function(i, e) {
				return $.attr(e, n, v);
			})
		},
		data: function(n, v) {
			return this.each(function(i, e) {
				return $.data(e, n, v);
			})
		},
		/**
		 * 设置/或者表单元素的值。没有参数获取值，有参数设置值
		 */
		val: function(val) {
			var _val = null;
			this.each(function(i, e) {
				//仅设置/获取表单元素的值
				if(e.form) {
					var n = e.name || e.id,
						t = e.type,
						tag = e.tagName.toLowerCase();
					//设置表单元素
					if(!$.isNull(val)) {
						if(tag == 'select') {
							$.each(e.options, function(i, _e) {
								if(_e.value == val) {
									_e.selected = 'selected';
									return false;
								}
							}, this);
						} else {
							e.value = val;
						}
					} else { //获取第一个表单元素 的值
						//处理select值
						if(tag == 'select') {
							var index = e.selectedIndex;
							//未选
							if(index < 0) {
								return null;
							}
							var a = [],
								ops = e.options;
							var one = (t == 'select-one'); //单选
							var max = (one ? index + 1 : ops.length);
							for(var i = (one ? index : 0); i < max; i++) {
								var op = ops[i];
								if(op.selected) {
									a.push(op.value);
								}
							}
							if(one) {
								_val = a[0];
							} else {
								_val = a;
							}
							//跳出循环 不再查找
							return false;
						}

						var val2;
						//处理radio,chekbox值
						if(t === 'radio') {
							//会多次查询，如何优化解决？
							var elems = e.form[n];
							$.each(elems, function(i, e) {
								if(e.checked) {
									val2 = e.value;
								}
							}, this);
						} else if(t === 'checkbox') {
							var elems = e.form[n],
								a = [];
							$.each(elems, function(i, e) {
								if(e.checked) {
									a.push(e.value);
								}
							}, this);
							val2 = a;
						} else {
							val2 = e.value;
						}
						_val = val2;
						//跳出循环 不再查找
						return false;
					}
				}

			});
			return val ? this : _val;
		},
		remove: function() {
			return this.each(function(i, e) {
				e.parentNode.removeChild(e)
			});
		}
	});
})(window);
/**
 * 事件
 * @param {Object} $
 */
(function($) {

	const handlers = {};
	const specialEvents = {};
	specialEvents.click = specialEvents.mousedown = specialEvents.mouseup = specialEvents.mousemove = 'MouseEvents';
	// every element and callback function will have an unique dtId
	var _dtId = 1;
	/**
	 * Get dtId of Element or callback function
	 * @param  {Object|Function} obj Element or callback function
	 * @return {Number} unique dtId
	 */
	function getDtId(obj) {
		return obj._dtId || (obj._dtId = _dtId++);
	}

	/**
	 * Get event object of event string, the first `.` is used to split event and namespace
	 *
	 * @param  {String} event Event type string with namespace or not
	 * @return {Object} An Object with `e` and `ns` key
	 */
	function parse(event) {
		const dotIndex = event.indexOf('.');
		if(dotIndex) {
			return {
				e: event.substring(0, event.indexOf('.')),
				ns: event.substring(dotIndex + 1, event.length)
			};
		}
		return {
			e: event
		};
	}

	/**
	 * Find matched event handlers
	 * @param  {Element} el the element to find
	 * @param  {String} selector Used by event delegation, null if not
	 * @param  {String} event Event string may with namespace
	 * @param  {Function} callback the callback to find, optional
	 * @return {Array} Array of handlers bind to el
	 */
	function findHandlers(el, selector, event, callback) {
		event = !$.isString(event) ? event : parse(event);
		return(handlers[getDtId(el)] || []).filter(function(handler) {
			return handler &&
				(!event.e || handler.e === event.e) &&
				(!event.ns || handler.ns === event.ns) &&
				(!callback || handler.callback === callback) &&
				(!selector || handler.selector === selector);
		});
	}

	function removeEvent(el, selector, event, callback) {
		event = parse(event);
		const eventName = event.e;
		const matchedHandlers = findHandlers(el, selector, event, callback);
		matchedHandlers.forEach(
			function(handler) {
				el.removeEventListener(eventName, handler.delegator || handler.callback);
				handler = null;
			}
		);
	}

	// delegator 只用于 delegate 时有用。
	function addEvent(el, selector, event, callback, delegator) {
		event = parse(event);
		const eventName = event.e,
			ns = event.ns;
		el.addEventListener(eventName, delegator || callback, false);

		// push events to handlers
		const id = getDtId(el),
			elHandlers = (handlers[id] || (handlers[id] = []));
		elHandlers.push({
			delegator: delegator,
			callback: callback,
			e: eventName,
			ns: ns,
			selector: selector
		});
	}
	$.Event = function(type, props) {
		if(!$.isString(type)) props = type, type = props.type;
		var bubbles = true;
		if(props) {
			for(var name in props) {
				if({}.hasOwnProperty.call(props, name)) {
					(name === 'bubbles') && (bubbles = !!props[name], delete props[name]);
				}
			}
		}
		return new CustomEvent(type, {
			'detail': props || {}
		});
	};
	$.fn.extend({
		on: function(selector, event, callback) {
			var delegator = null;
			callback = callback ? delegator = function(e) {
				const els = el.querySelectorAll(selector);
				var matched = false;
				$.each(els, function(el) {
					if(el === e.target || el.contains(e.target)) {
						matched = el;
						return false;
					}
				});
				if(matched) {
					callback.apply(matched, [].slice.call(arguments));
				}
			} : event;
			event = callback ? event : selector;
			selector = callback ? selector : null;

			return this.each(function(i, e) {
				addEvent(e, selector, event, callback, delegator);
			});
		},
		off: function(event, callback) {
			return this.each(function(i, e) {
				removeEvent(e, null, event, callback);
			});
		},
		once: function(event, callback) {
			var that = this;

			function recursiveFunction(e) {
				$(e.currentTarget).off(e.type, recursiveFunction);
				return callback(e);
			};
			return this.on(event, recursiveFunction);
		},
		// Dispatch an event with props to el
		trigger: function(event, props) {
			if($.isString(event)) event = $.Event(event, props);
			return this.each(function(i, e) {
				e.dispatchEvent(event);
			});
		}
	})
})(omm);
/**
 * 表单对象,序列化表单、验证表单
 * @param {Object} $ omm
 */
(function($) {
	//简单表单对象
	//表单类
	$.Form = (function() {
		//rules格式{all:[{fn:fn,msg:""}]}
		var rules = {
			all: [],
			password: [],
			email: [],
			url: [],
			number: []
		};
		var Form = function(form) {
			this.form = form;
			this.data = {}; //字段值，{name1:val1,name2:val2,name3:[]}
			this.errors = {}; //格式{element:[msg1,msg2...]}
			this.init();
		};
		Form.prototype = {
			init: function() {},
			valid: function() {
				this.clear();
				this.each(this._validField);
				return $.isEmpty(this.errors);
			},
			_validField: function(el) {
				var val = this._fieldValue(el);
				//不需要验证
				if(el.novalidate && $.attr(field, "novalidate") != "false") return true;
				var type = el.type,
					name = el.name || el.id,
					_rules = [];

				//验证规则
				_rules = _rules.concat(rules.all)
				if(rules[type]) {
					_rules = _rules.concat(rules[type]);
				};
				$.each(_rules, function(i, e) {
					//验证
					var r = e.fn.call(this, el, val);
					//如果验证失败
					if(!r) {
						this.errors[name] || (this.errors[name] = []);
						var msg = e.name ? el.dataset["valid" + $.upperFirst(e.name) + "Msg"] : "";
						msg || (msg = el.dataset.validMsg);
						msg || (msg = $.attr(el, "placeholder"));
						msg = msg ? msg : e.msg ? e.msg.replace("{1}", name) : "未知错误位于" + name;
						this.errors[name].push({
							msg: msg,
							el: el
						});
					}
				}, this);
			},
			_fieldValue: function(el) {
				var n = el.name || el.id;
				var val = $(el).val();
				if(val) this.data[n] = val;
				return val;
			},
			/**
			 * 返回json对象类型的数据
			 */
			getData: function() {
				return this.data;
			},
			/**
			 * 返回序列化字段值对，用于url参数传递
			 */
			serialize: function() {
				var s = "";
				for(var n in this.data) {
					var v = this.data[n];
					if(v && v.constructor == Array) {
						$.each(v, function(i, e) {
							s += "&" + n + "=" + e; //??
						}, this);
					} else if(v !== null && typeof v !== 'undefined') {
						s += "&" + n + "=" + v; //???
					}
				}
				return s.length == 0 ? s : s.substr(1);
			},
			getErrors: function() {
				return this.errors;
			},
			getError: function(i) {
				var j = 0;
				for(var e in this.errors) {
					if(j == i) return this.errors[e];
					j++;
				}
				return null;
			},
			/**
			 * 清空
			 */
			clear: function() {
				this.data = {};
				this.errors = {};
			},
			each: function(fn) {
				var elems = this.form.elements;
				$.each(elems, function(i, e) {
					var ty = e.type;
					if(e.nodeType == 1 && e.name && ty && !e.disabled && ty != 'reset' && ty != 'button' && ty != 'submit' && ty != 'image' && ty != 'file') { //input元素
						fn.call(this, e);
					}
				}, this);
			},
			reset: function(hidden, excludes) {
				var h = false;
				if($.isBoolean(hidden)) {
					h = hidden;
				} else {
					excludes = hidden;
				}
				this.each(function(e) {
					if(h || e.type != "hidden") {
						$(e).val("");
					};
				});
			}
		};
		Form.addRule = function(rule) {};
		//所有验证
		var valids = {
			required: {
				fn: function(field, val) {
					if(field.required && $.attr(field, "required") != "false") {
						return !$.isBlank(val);
					}
					return true;
				},
				msg: "字段{1}必填",
				name: "required"
			},
			equals: {
				fn: function(field, val) {
					var matchName = field.dataset.validEquals;
					if(matchName) {
						var el = field.form[matchName];
						if(el) {
							return val === $(el).val();
						} else {
							return false;
						}
					}
					return true;
				},
				msg: "字段{1}值必须等于字段{2}值",
				name: "equals"
			},
			email: {
				fn: function(field, val) {
					return $.isEmail(val);
				},
				msg: "字段{1}要求邮件格式",
				name: "email"
			},
			min_length: {
				fn: function(field, val) {
					var m = field.getAttribute("min-length")
					if(m) {
						return(val.length >= parseInt(m, 10));
					}
					return true;
				},
				msg: "字段{1}字符串长度不够",
				name: "minLength"
			},
			max_length: {
				fn: function(field, val) {
					var m = field.getAttribute("min-length")
					if(m) {
						return(val.length <= parseInt(m, 10));
					}
					return true;
				},
				msg: "字段{1}字符串长超长",
				name: "maxLength"
			},
			url: {
				fn: function(field, val) {
					return $.isUrl(val);
				},
				msg: "字段{1}要求url格式",
				name: "url"
			}
		};
		//添加验证规则
		rules.all.push(valids.required);
		rules.all.push(valids.min_length);
		rules.all.push(valids.max_length);
		rules.password.push(valids.equals);
		rules.email.push(valids.email);
		rules.url.push(valids.url);
		return Form;
	})();
	$.fn.form = function(fn, opts) {
		if(!fn) $.error("表单操作回调函数不能为空");
		this.each(function(i, e) {
			if(e.tagName.toLowerCase() == "form") {
				fn.call(this, new $.Form(e, opts))
			}
		})
		return this;
	}
})(omm);